#ifndef HITSZ_DIP_EX4_DETECTOR_H
#define HITSZ_DIP_EX4_DETECTOR_H

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <algorithm>

namespace dip
{
    enum class EX4_COLOR{
        NONE = 0,
        RED=1,ORANGE=2,YELLOW=3,
        GREEN=4,CYAN=5,BLUE=6,PURPLE=7
    };
    //int转为EX4_COLOR
    inline EX4_COLOR int2ex4_color(int type)
    {
        switch(type)
        {
            case 1 : return EX4_COLOR::RED;break;
            case 2 : return EX4_COLOR::ORANGE;break;
            case 3 : return EX4_COLOR::YELLOW;break;
            case 4 : return EX4_COLOR::GREEN;break;
            case 5 : return EX4_COLOR::CYAN;break;
            case 6 : return EX4_COLOR::BLUE;break;
            case 7 : return EX4_COLOR::PURPLE;break;
            default: return EX4_COLOR::NONE; break;    
        }
    }

    struct HSV_Threshold
    {
        int h_min;
        int h_max;
        int s_min;
        int s_max;
        int v_min;
        int v_max;
    };
    //默认HSV阈值参数
    int DEFAULT_HSV_HMIN[] = {156,11,26,35,78,100,125};
    int DEFAULT_HSV_HMAX[] = {180,25,34,77,99,124,155};
    int DEFAULT_HSV_SMIN[] = {43,43,43,43,43,43,43};
    int DEFAULT_HSV_SMAX[] = {255,255,255,255,255,255,255};
    int DEFAULT_HSV_VMIN[] = {46,46,46,46,46,46,46};
    int DEFAULT_HSV_VMAX[] = {255,255,255,255,255,255,255};
    
    inline double calc_angle(cv::Point pt1, cv::Point pt2, cv::Point pt0)
    {
	    double dx1 = pt1.x - pt0.x;
	    double dy1 = pt1.y - pt0.y;
	    double dx2 = pt2.x - pt0.x;
	    double dy2 = pt2.y - pt0.y;
	    return (dx1*dx2 + dy1*dy2) / sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
    }

    class Ex4Detector
    {
        public:
            Ex4Detector()
            {
                this->init_threshold();
            }
            ~Ex4Detector(){};

            /**
             * @brief 判断ROI内最接近的颜色 
             * @param input,输入图像，应该为HSV类型
             * @param rect ,截取roi，注意，该roi必须合法
             * @param pixel_counts,各个像素的数目
             * @return 
             * @note 利用最为简单的统计法，分别利用各个颜色对应阈值进行二值化，统计像素数目,当某一颜色对应
             * 像素个数>50%提前终止;当各个颜色的值都小于1/6总像素值认为判断失败
             */ 
            EX4_COLOR assert_label_color(const cv::Mat &input,const cv::Rect &rect,std::vector<int> &pixel_counts)
            {
                int all_pixel_nums = rect.width*rect.height;//总像素数目
                cv::Mat roi = input(rect).clone();//拷贝roi,其实不clone也可以
                cv::Mat thre_img;//颜色分割后二值图
                pixel_counts.clear();
                int max_pixel_nums = 0;//最大像素数
                int max_id = -1;//最大像素数对应索引
                for (int i=0;i<this->hsv_thre_list.size();i++)
                { //遍历各个颜色
                    auto hsv_thre = hsv_thre_list[i];
                    cv::inRange(roi,cv::Scalar(hsv_thre.h_min,hsv_thre.s_min,hsv_thre.v_min), 
                        cv::Scalar(hsv_thre.h_max,hsv_thre.s_max,hsv_thre.v_max),thre_img); //Threshold the image 
                    
                    //统计像素个数
                    int color_pixel_nums = cv::countNonZero(thre_img);
                    pixel_counts.push_back(color_pixel_nums);
                    if (color_pixel_nums > all_pixel_nums/2)
                    { //如果对应颜色像素点数目>一半
                        max_pixel_nums = color_pixel_nums;
                        max_id = i;
                        break;
                    }

                    if (max_pixel_nums < all_pixel_nums /6)
                    { //if to avg
                        max_id = -1;
                    }

                    //对颜色进行排序
                    if (color_pixel_nums > max_pixel_nums)
                    {
                        max_pixel_nums = color_pixel_nums;
                        max_id = i;
                    }

                }
                return int2ex4_color(max_id+1);

            }

            /**
             * @brief 判断label对应的颜色,并检测label
             * @param img,传入图像,应该为BGR格式
             * @param output,输出图像 vector,主要是用于debug
             * @param pixel_counts
             * @param enable_debug,是否开启debug，如果开启会将output进行绘制
             */ 
            EX4_COLOR detect_label(const cv::Mat &img,std::vector<cv::Mat> &output,std::vector<int> &pixel_counts,bool enable_debug)
            {
                cv::Mat hsv_img,gray_img,binary_img; 
                cv::cvtColor(img,hsv_img,cv::COLOR_BGR2HSV);//颜色通道转换
                cv::cvtColor(img,gray_img,cv::COLOR_BGR2GRAY);

                //hsv segmant
                std::vector<EX4_COLOR> vColor = {EX4_COLOR::RED,EX4_COLOR::YELLOW,EX4_COLOR::GREEN,EX4_COLOR::BLUE};
                this->segmant_color(hsv_img,vColor,output);

                //findcounter
                //从各个二值化图像中各提取一个rect
                std::vector<cv::Rect> vBoundingBox;
                
                for (int k=0;k<output.size();k++)
                {
                    std::vector<std::vector<cv::Point>> vContours; //轮廓
                    cv::findContours(output[k],vContours,cv::RETR_EXTERNAL,cv::CHAIN_APPROX_NONE);
                    //多边形拟合
                    std::vector<std::vector<cv::Point>> vRects; //为矩形的轮廓
                    for (int i=0;i<vContours.size();i++)
                    { //判断轮廓是否为四边形
                        std::vector<cv::Point> approxPloy;
                        float epsilon = cv::arcLength(vContours[i], true)*0.02;
                        cv::approxPolyDP(vContours[i], approxPloy, epsilon, true);
                        if (approxPloy.size() == 4 &&fabs(cv::contourArea(approxPloy)) > 1000 &&cv::isContourConvex(approxPloy))
                        { //计算余弦,满足条件为矩形
                            double maxCosine = 0;

                            for (int j = 2; j < 5; j++)
                            {
                                // 求轮廓边缘之间角度的最大余弦
                                double cosine = fabs(calc_angle(approxPloy[j % 4],approxPloy[j - 2],approxPloy[j - 1]));
                                maxCosine = MAX(maxCosine, cosine);
                            }

                            if (maxCosine < 0.3)
                            {
                                vRects.push_back(approxPloy);
                            }
                        }
                    }

                    //提取面积最大的矩形
                    if (vRects.size()==0)
                    { //如果没有合适的矩形
                        continue;
                    }
                    //如果找到了矩形
                    
                    double max_area = 0;
                    int index = 0;
                    for (int j=0;j<vRects.size();j++)
                    { //find max rect
                        double area = cv::contourArea(vRects[j],false);
                        if (area > max_area)
                        {
                            max_area = area;
                            index = j;
                        }
                    }
                    auto objRect = vRects[index];

                    //提取面积最大的矩形的BoundingRect作为roi
                    cv::Rect boundRect = cv::boundingRect(objRect);
                    vBoundingBox.push_back(boundRect);
                }

                output.push_back(hsv_img.clone());
                if (vBoundingBox.size() == 0)
                { //如果没有提取到四边形
                    return EX4_COLOR::NONE;
                }

                if (enable_debug)
                { //如果开启debug
                    //似乎啥都不干
                }

                int max_area = 0;
                int index = 0;
                for (int i=0;i<vBoundingBox.size();i++)
                { //获取最大面积的矩形
                    int area = vBoundingBox[i].area();
                    if (area > max_area)
                    {
                        max_area = area ;
                        index  = i;
                    }
                }

                //判断颜色
                // std::vector<int> pixel_counts;
                EX4_COLOR color = this->assert_label_color(hsv_img,vBoundingBox[index],pixel_counts);
                // output.push_back(hsv_img.clone());
                return color;
            }
            
            /**
             * @brief hsv图像提取各个颜色的二值化图像
             * @param src ，hsv图像
             * @param color,需要分割的颜色
             * @param output，输出的mat vector
             */ 
            void segmant_color(const cv::Mat &src,const std::vector<EX4_COLOR> &vColor,std::vector<cv::Mat> &output)
            {
                int n = vColor.size();
                output.clear();
                cv::Mat thre_img;
                for (int i=0;i<n;i++)
                { //对每个颜色进行分割
                    EX4_COLOR color = vColor[i];
                    HSV_Threshold hsv_thre = this->hsv_thre_list[static_cast<int>(color)-1];
                    cv::inRange(src,cv::Scalar(hsv_thre.h_min,hsv_thre.s_min,hsv_thre.v_min), 
                        cv::Scalar(hsv_thre.h_max,hsv_thre.s_max,hsv_thre.v_max),thre_img); //Threshold the image 
                    output.push_back(thre_img.clone());
                }
            }
        private:
            void init_threshold(void)
            {
                hsv_thre_list.clear();
                hsv_thre_list.resize(7);//和ex4_color一样的长度和顺序
                for (int i=0;i<7;i++)
                {
                    hsv_thre_list[i].h_min = DEFAULT_HSV_HMIN[i];
                    hsv_thre_list[i].h_max = DEFAULT_HSV_HMAX[i];
                    hsv_thre_list[i].s_min = DEFAULT_HSV_SMIN[i];
                    hsv_thre_list[i].s_max = DEFAULT_HSV_SMAX[i];
                    hsv_thre_list[i].v_min = DEFAULT_HSV_VMIN[i];
                    hsv_thre_list[i].v_max = DEFAULT_HSV_VMAX[i];
                    std::cout <<"init HSV threshold i:"<<i<<",h:";
                    std::cout<<hsv_thre_list[i].h_min<<","<<hsv_thre_list[i].h_max<<",s:";
                    std::cout<<hsv_thre_list[i].s_min<<","<<hsv_thre_list[i].s_max<<",v:";
                    std::cout<<hsv_thre_list[i].v_min<<","<<hsv_thre_list[i].v_max<<std::endl;
                }
                this->binary_threshold = -1;
            }

        public:
            int binary_threshold;
            std::vector<HSV_Threshold> hsv_thre_list; //HSV阈值

        private:

    };
}
#endif